﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Nirvana
{
    public class MaterialPool : Singleton<MaterialPool>
    {
        private static Logger logger = LogSystem.GetLogger("MaterialPool");

        private Dictionary<AssetID, MaterialCache> matAssetIdMap = new Dictionary<AssetID, MaterialCache>();
        private Dictionary<Material, MaterialCache> matMap = new Dictionary<Material, MaterialCache>();
        private float defaultReleaseAfterFree = 30f;
        public float DefaultReleaseAfterFree
        {
            get
            {
                return defaultReleaseAfterFree;
            }
            set
            {
                defaultReleaseAfterFree = value;
                foreach (var p in matAssetIdMap)
                    p.Value.SetReleaseTimeAfterFree(value);
            }
        }

        public MaterialPool()
        {
            Scheduler.AddFrameListener(() => { matAssetIdMap.RemoveAll(Remove); });
        }

        public Dictionary<AssetID, MaterialCache> GetCacheAssetIDMap()
        {
            return matAssetIdMap;
        }

        public void Load(AssetID assetID, Action<Material> complete)
        {
            Scheduler.RunCoroutine(this.LoadScriptASync(assetID, complete));
        }

        public WaitLoadMaterial Load(AssetID assetid)
        {
            MaterialCache cache;
            if (this.matAssetIdMap.TryGetValue(assetid, out cache))
            {
                cache.Retain();
                return new WaitLoadMaterial(cache);
            }

            cache = new MaterialCache(assetid, this.matMap);
            cache.Load(assetid);
            cache.Retain();
            this.matAssetIdMap.Add(assetid, cache);
            return new WaitLoadMaterial(cache);
        }

        public void Free(Material mat, bool destroy = false)
        {
            if (mat == null)
            {
                logger.LogError("Try to free a null Material.");
                return;
            }
            MaterialCache cache;
            if (!this.matMap.TryGetValue(mat, out cache))
            {
                logger.LogWarning("Try to free an instance {0} not allocated by this pool.", mat.name);
                return;
            }
            cache.Release();
            if (destroy && cache.ReferenceCount == 0)
            {
                AssetID assetID = cache.AssetID;
                var obj = cache.GetObject();
                if (obj != null)
                {
                    AssetManager.UnloadAsseBundle(assetID.BundleName);
                    this.matMap.Remove(obj);
                }
                this.matAssetIdMap.Remove(assetID);
            }
        }

        public void Clear()
        {
            this.matAssetIdMap.Clear();
            this.matMap.Clear();
        }

        public void ClearAllUnused()
        {
            this.matAssetIdMap.RemoveAll(Unload);
        }

        private IEnumerator LoadScriptASync(AssetID assetid, Action<Material> cb)
        {
            WaitLoadMaterial wait = this.Load(assetid);
            if (wait.keepWaiting)
            {
                yield return wait;
            }
            if (!string.IsNullOrEmpty(wait.Error))
            {
                logger.LogError(wait.Error);
                cb(null);
            }
            else
            {
                cb(wait.LoadedObject);
            }
            yield break;
        }

        private bool Remove(AssetID asset, MaterialCache cache)
        {
            if (!string.IsNullOrEmpty(cache.Error))
            {
                return true;
            }
            if (cache.ReferenceCount != 0)
            {
                return false;
            }
            float _t = cache.LastFreeTime + cache.ReleaseAfterFree;
            if (Time.realtimeSinceStartup > _t)
            {
                var obj = cache.GetObject();
                if (obj != null)
                {
                    this.matMap.Remove(obj);
                    AssetManager.UnloadAsseBundle(asset.BundleName);
                }
                return true;
            }
            return false;
        }

        private bool Unload(AssetID assetid, MaterialCache cache)
        {
            if (!string.IsNullOrEmpty(cache.Error))
            {
                return true;
            }

            if (cache.ReferenceCount != 0)
            {
                return false;
            }

            var obj = cache.GetObject();
            if (obj != null)
            {
                this.matMap.Remove(obj);
                AssetManager.UnloadAsseBundle(assetid.BundleName);
            }
            return true;
        }
    }
}


